package com.winning.pmph.mapper;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.lang.NonNull;

import javax.persistence.Table;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public interface PMPHBaseMapper<T> extends BaseMapper<T> {

    default Class<T> getGenericClazz(){
        // 获取到具体的mapperClass;
        Class mapperClazz = (Class) getClass().getGenericInterfaces()[0];
        // 获取到PMPHBaseMapper接口带泛型的Class;
        ParameterizedType pmphBaseMapperClazzWithGeneric = (ParameterizedType) mapperClazz.getGenericInterfaces()[0];
        // 获取泛型指定的实体类;
        return (Class) pmphBaseMapperClazzWithGeneric.getActualTypeArguments()[0];
    }

    default String getTableName() {
        return getGenericClazz().getAnnotation(Table.class).name();
    }

    /**
     * @param t
     * @param <T>
     * @return
     */
    default  <T> Serializable getId(@NonNull T t) {
        Serializable id = null;
        try {
            Class crtClass = t.getClass();
            Optional<Field> idFieldOptional;
            while (true) {
                idFieldOptional = Arrays.stream(crtClass.getDeclaredFields())
                        .filter(field -> Objects.nonNull(field.getAnnotation(TableId.class)))
                        .findFirst();
                if (idFieldOptional.isPresent()) {
                    break;
                }
                crtClass = crtClass.getSuperclass();
            }
            Field idField = idFieldOptional.get();
            idField.setAccessible(true);
            id = (Serializable) idField.get(t);
        } catch (Exception e) {
            throw new RuntimeException("未找到实体类id字段!");
        }
        return id;
    }

    default T saveBean(@NonNull T t) {
        Serializable id = getId(t);
        if (Objects.isNull(id) || Objects.isNull(selectById(id))) {
            insert(t);
        }
        else {
            updateById(t);
        }
        return t;
    }

    default int deleteBean(@NonNull T t) {
        Serializable id = getId(t);
        return deleteById(id);
    }

    default List<T> selectBeansByWherePart(String whereStr) {
        String sql = "select * from " + getTableName();
        if (StringUtils.isNotEmpty(whereStr)) {
            sql += " where " + whereStr;
        }
        return executeMultiSelect(sql);
    }

    default List<T> selectAll() {
        String sql = "select * from " + getTableName();
        return executeMultiSelect(sql);
    }

    default T selectBeanByWherePart(String whereStr) {
        String sql = "select * from " + getTableName();
        if (StringUtils.isNotEmpty(whereStr)) {
            sql += " where " + whereStr;
        }
        return executeSingleSelect(sql);
    }

    default int deleteBeansByWherePart(String whereStr) {
        String sql = "delete from " + getTableName();
        if (StringUtils.isNotEmpty(whereStr)) {
            sql += " where " + whereStr;
        }
        return executeDelete(sql);
    }

    @Select("${sql}")
    int selectInt(String sql);

    @Select("${sql}")
    String selectStr(String sql);

    @Select("${sql}")
    T executeSingleSelect(String sql);

    @Select("${sql}")
    List<T> executeMultiSelect(String sql);

    @Insert("${sql}")
    int executeInsert(String sql);

    @Update("${sql}")
    int executeUpdate(String sql);

    @Delete("${sql}")
    int executeDelete(String sql);

    default LambdaQueryChainWrapper<T> lambdaQuery() {
        return new LambdaQueryChainWrapper<>(this);
    }

    default T one(LambdaQueryChainWrapper<T> queryWrapper) {
        if (Objects.isNull(queryWrapper)) {
            queryWrapper = lambdaQuery();
        }
        queryWrapper.last("LIMIT 1");
        return queryWrapper.one();
    }


    default T saveOrUpdate(T entity) {
        Class<?> cls = entity.getClass();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
        Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
        String keyProperty = tableInfo.getKeyProperty();
        Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
        Serializable idVal = (Serializable) ReflectionKit.getFieldValue(entity, tableInfo.getKeyProperty());
        if (Objects.isNull(idVal) || Objects.isNull(selectById(idVal))) {
            insert(entity);
        } else {
            updateById(entity);
        }
        return entity;
    }
}
